// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/gl/gl_gl_api_implementation.h"

#include <vector>

#include "base/command_line.h"
#include "base/stl_util.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_implementation.h"
#include "ui/gl/gl_state_restorer.h"
#include "ui/gl/gl_surface.h"
#include "ui/gl/gl_switches.h"
#include "ui/gl/gl_version_info.h"

namespace gfx {

// The GL Api being used. This could be g_real_gl or gl_trace_gl
static GLApi* g_gl = NULL;
// A GL Api that calls directly into the driver.
static RealGLApi* g_real_gl = NULL;
// A GL Api that does nothing but warn about illegal GL calls without a context
// current.
static NoContextGLApi* g_no_context_gl = NULL;
// A GL Api that calls TRACE and then calls another GL api.
static TraceGLApi* g_trace_gl = NULL;
// GL version used when initializing dynamic bindings.
static GLVersionInfo* g_version_info = NULL;

namespace {

    static inline GLenum GetInternalFormat(GLenum internal_format)
    {
        if (gfx::GetGLImplementation() != gfx::kGLImplementationEGLGLES2) {
            if (internal_format == GL_BGRA_EXT || internal_format == GL_BGRA8_EXT)
                return GL_RGBA8;
        }
        return internal_format;
    }

    // TODO(epenner): Could the above function be merged into this and removed?
    static inline GLenum GetTexInternalFormat(GLenum internal_format,
        GLenum format,
        GLenum type)
    {
        GLenum gl_internal_format = GetInternalFormat(internal_format);

        // g_version_info must be initialized when this function is bound.
        DCHECK(gfx::g_version_info);
        if (gfx::g_version_info->is_es3) {
            if (internal_format == GL_RED_EXT) {
                // GL_EXT_texture_rg case in ES2.
                switch (type) {
                case GL_UNSIGNED_BYTE:
                    gl_internal_format = GL_R8_EXT;
                    break;
                case GL_HALF_FLOAT_OES:
                    gl_internal_format = GL_R16F_EXT;
                    break;
                case GL_FLOAT:
                    gl_internal_format = GL_R32F_EXT;
                    break;
                default:
                    NOTREACHED();
                    break;
                }
                return gl_internal_format;
            } else if (internal_format == GL_RG_EXT) {
                // GL_EXT_texture_rg case in ES2.
                switch (type) {
                case GL_UNSIGNED_BYTE:
                    gl_internal_format = GL_RG8_EXT;
                    break;
                case GL_HALF_FLOAT_OES:
                    gl_internal_format = GL_RG16F_EXT;
                    break;
                case GL_FLOAT:
                    gl_internal_format = GL_RG32F_EXT;
                    break;
                default:
                    NOTREACHED();
                    break;
                }
                return gl_internal_format;
            }
        }

        if (type == GL_FLOAT && gfx::g_version_info->is_angle && gfx::g_version_info->is_es && gfx::g_version_info->major_version == 2) {
            // It's possible that the texture is using a sized internal format, and
            // ANGLE exposing GLES2 API doesn't support those.
            // TODO(oetuaho@nvidia.com): Remove these conversions once ANGLE has the
            // support.
            // http://code.google.com/p/angleproject/issues/detail?id=556
            switch (format) {
            case GL_RGBA:
                gl_internal_format = GL_RGBA;
                break;
            case GL_RGB:
                gl_internal_format = GL_RGB;
                break;
            default:
                break;
            }
        }

        if (gfx::g_version_info->is_es)
            return gl_internal_format;

        if (type == GL_FLOAT) {
            switch (internal_format) {
            // We need to map all the unsized internal formats from ES2 clients.
            case GL_RGBA:
                gl_internal_format = GL_RGBA32F_ARB;
                break;
            case GL_RGB:
                gl_internal_format = GL_RGB32F_ARB;
                break;
            case GL_LUMINANCE_ALPHA:
                gl_internal_format = GL_LUMINANCE_ALPHA32F_ARB;
                break;
            case GL_LUMINANCE:
                gl_internal_format = GL_LUMINANCE32F_ARB;
                break;
            case GL_ALPHA:
                gl_internal_format = GL_ALPHA32F_ARB;
                break;
            default:
                // We can't assert here because if the client context is ES3,
                // all sized internal_format will reach here.
                break;
            }
        } else if (type == GL_HALF_FLOAT_OES) {
            switch (internal_format) {
            case GL_RGBA:
                gl_internal_format = GL_RGBA16F_ARB;
                break;
            case GL_RGB:
                gl_internal_format = GL_RGB16F_ARB;
                break;
            case GL_LUMINANCE_ALPHA:
                gl_internal_format = GL_LUMINANCE_ALPHA16F_ARB;
                break;
            case GL_LUMINANCE:
                gl_internal_format = GL_LUMINANCE16F_ARB;
                break;
            case GL_ALPHA:
                gl_internal_format = GL_ALPHA16F_ARB;
                break;
            default:
                NOTREACHED();
                break;
            }
        }
        return gl_internal_format;
    }

    static inline GLenum GetTexType(GLenum type)
    {
        if (gfx::GetGLImplementation() != gfx::kGLImplementationEGLGLES2) {
            if (type == GL_HALF_FLOAT_OES)
                return GL_HALF_FLOAT_ARB;
        }
        return type;
    }

    static void GL_BINDING_CALL CustomTexImage2D(
        GLenum target, GLint level, GLint internalformat,
        GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type,
        const void* pixels)
    {
        GLenum gl_internal_format = GetTexInternalFormat(
            internalformat, format, type);
        GLenum gl_type = GetTexType(type);
        g_driver_gl.orig_fn.glTexImage2DFn(
            target, level, gl_internal_format, width, height, border, format, gl_type,
            pixels);
    }

    static void GL_BINDING_CALL CustomTexSubImage2D(
        GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width,
        GLsizei height, GLenum format, GLenum type, const void* pixels)
    {
        GLenum gl_type = GetTexType(type);
        g_driver_gl.orig_fn.glTexSubImage2DFn(
            target, level, xoffset, yoffset, width, height, format, gl_type, pixels);
    }

    static void GL_BINDING_CALL CustomTexStorage2DEXT(
        GLenum target, GLsizei levels, GLenum internalformat, GLsizei width,
        GLsizei height)
    {
        GLenum gl_internal_format = GetInternalFormat(internalformat);
        g_driver_gl.orig_fn.glTexStorage2DEXTFn(
            target, levels, gl_internal_format, width, height);
    }

    static void GL_BINDING_CALL CustomRenderbufferStorageEXT(
        GLenum target, GLenum internalformat, GLsizei width, GLsizei height)
    {
        GLenum gl_internal_format = GetInternalFormat(internalformat);
        g_driver_gl.orig_fn.glRenderbufferStorageEXTFn(
            target, gl_internal_format, width, height);
    }

    // The ANGLE and IMG variants of glRenderbufferStorageMultisample currently do
    // not support BGRA render buffers so only the EXT one is customized. If
    // GL_CHROMIUM_renderbuffer_format_BGRA8888 support is added to ANGLE then the
    // ANGLE version should also be customized.
    static void GL_BINDING_CALL CustomRenderbufferStorageMultisampleEXT(
        GLenum target, GLsizei samples, GLenum internalformat, GLsizei width,
        GLsizei height)
    {
        GLenum gl_internal_format = GetInternalFormat(internalformat);
        g_driver_gl.orig_fn.glRenderbufferStorageMultisampleEXTFn(
            target, samples, gl_internal_format, width, height);
    }

} // anonymous namespace

void DriverGL::InitializeCustomDynamicBindings(GLContext* context)
{
    InitializeDynamicBindings(context);

    DCHECK(orig_fn.glTexImage2DFn == NULL);
    orig_fn.glTexImage2DFn = fn.glTexImage2DFn;
    fn.glTexImage2DFn = reinterpret_cast<glTexImage2DProc>(CustomTexImage2D);

    DCHECK(orig_fn.glTexSubImage2DFn == NULL);
    orig_fn.glTexSubImage2DFn = fn.glTexSubImage2DFn;
    fn.glTexSubImage2DFn = reinterpret_cast<glTexSubImage2DProc>(CustomTexSubImage2D);

    DCHECK(orig_fn.glTexStorage2DEXTFn == NULL);
    orig_fn.glTexStorage2DEXTFn = fn.glTexStorage2DEXTFn;
    fn.glTexStorage2DEXTFn = reinterpret_cast<glTexStorage2DEXTProc>(CustomTexStorage2DEXT);

    DCHECK(orig_fn.glRenderbufferStorageEXTFn == NULL);
    orig_fn.glRenderbufferStorageEXTFn = fn.glRenderbufferStorageEXTFn;
    fn.glRenderbufferStorageEXTFn = reinterpret_cast<glRenderbufferStorageEXTProc>(
        CustomRenderbufferStorageEXT);

    DCHECK(orig_fn.glRenderbufferStorageMultisampleEXTFn == NULL);
    orig_fn.glRenderbufferStorageMultisampleEXTFn = fn.glRenderbufferStorageMultisampleEXTFn;
    fn.glRenderbufferStorageMultisampleEXTFn = reinterpret_cast<glRenderbufferStorageMultisampleEXTProc>(
        CustomRenderbufferStorageMultisampleEXT);
}

static void GL_BINDING_CALL NullDrawClearFn(GLbitfield mask)
{
    if (!g_driver_gl.null_draw_bindings_enabled)
        g_driver_gl.orig_fn.glClearFn(mask);
}

static void GL_BINDING_CALL
NullDrawDrawArraysFn(GLenum mode, GLint first, GLsizei count)
{
    if (!g_driver_gl.null_draw_bindings_enabled)
        g_driver_gl.orig_fn.glDrawArraysFn(mode, first, count);
}

static void GL_BINDING_CALL NullDrawDrawElementsFn(GLenum mode,
    GLsizei count,
    GLenum type,
    const void* indices)
{
    if (!g_driver_gl.null_draw_bindings_enabled)
        g_driver_gl.orig_fn.glDrawElementsFn(mode, count, type, indices);
}

void DriverGL::InitializeNullDrawBindings()
{
    DCHECK(orig_fn.glClearFn == NULL);
    orig_fn.glClearFn = fn.glClearFn;
    fn.glClearFn = NullDrawClearFn;

    DCHECK(orig_fn.glDrawArraysFn == NULL);
    orig_fn.glDrawArraysFn = fn.glDrawArraysFn;
    fn.glDrawArraysFn = NullDrawDrawArraysFn;

    DCHECK(orig_fn.glDrawElementsFn == NULL);
    orig_fn.glDrawElementsFn = fn.glDrawElementsFn;
    fn.glDrawElementsFn = NullDrawDrawElementsFn;

    null_draw_bindings_enabled = true;
}

bool DriverGL::HasInitializedNullDrawBindings()
{
    return orig_fn.glClearFn != NULL && orig_fn.glDrawArraysFn != NULL && orig_fn.glDrawElementsFn != NULL;
}

bool DriverGL::SetNullDrawBindingsEnabled(bool enabled)
{
    DCHECK(orig_fn.glClearFn != NULL);
    DCHECK(orig_fn.glDrawArraysFn != NULL);
    DCHECK(orig_fn.glDrawElementsFn != NULL);

    bool before = null_draw_bindings_enabled;
    null_draw_bindings_enabled = enabled;
    return before;
}

void InitializeStaticGLBindingsGL()
{
    g_current_gl_context_tls = new base::ThreadLocalPointer<GLApi>;
    g_driver_gl.InitializeStaticBindings();
    if (!g_real_gl) {
        g_real_gl = new RealGLApi();
        g_trace_gl = new TraceGLApi(g_real_gl);
        g_no_context_gl = new NoContextGLApi();
    }
    g_real_gl->Initialize(&g_driver_gl);
    g_gl = g_real_gl;
    if (base::CommandLine::ForCurrentProcess()->HasSwitch(
            switches::kEnableGPUServiceTracing)) {
        g_gl = g_trace_gl;
    }
    SetGLToRealGLApi();
}

GLApi* GetCurrentGLApi()
{
    return g_current_gl_context_tls ? g_current_gl_context_tls->Get() : nullptr;
}

void SetGLApi(GLApi* api)
{
    g_current_gl_context_tls->Set(api);
}

void SetGLToRealGLApi()
{
    SetGLApi(g_gl);
}

void SetGLApiToNoContext()
{
    SetGLApi(g_no_context_gl);
}

const GLVersionInfo* GetGLVersionInfo()
{
    return g_version_info;
}

void InitializeDynamicGLBindingsGL(GLContext* context)
{
    if (g_version_info)
        return;
    g_real_gl->InitializeFilteredExtensions();
    g_driver_gl.InitializeCustomDynamicBindings(context);
    DCHECK(context && context->IsCurrent(NULL) && !g_version_info);
    g_version_info = new GLVersionInfo(
        context->GetGLVersion().c_str(),
        context->GetGLRenderer().c_str(),
        context->GetExtensions().c_str());
}

void InitializeDebugGLBindingsGL()
{
    g_driver_gl.InitializeDebugBindings();
}

void InitializeNullDrawGLBindingsGL()
{
    g_driver_gl.InitializeNullDrawBindings();
}

bool HasInitializedNullDrawGLBindingsGL()
{
    return g_driver_gl.HasInitializedNullDrawBindings();
}

bool SetNullDrawGLBindingsEnabledGL(bool enabled)
{
    return g_driver_gl.SetNullDrawBindingsEnabled(enabled);
}

void ClearGLBindingsGL()
{
    if (g_real_gl) {
        delete g_real_gl;
        g_real_gl = NULL;
    }
    if (g_trace_gl) {
        delete g_trace_gl;
        g_trace_gl = NULL;
    }
    if (g_no_context_gl) {
        delete g_no_context_gl;
        g_no_context_gl = NULL;
    }
    g_gl = NULL;
    g_driver_gl.ClearBindings();
    if (g_current_gl_context_tls) {
        delete g_current_gl_context_tls;
        g_current_gl_context_tls = NULL;
    }
    if (g_version_info) {
        delete g_version_info;
        g_version_info = NULL;
    }
}

GLApi::GLApi()
{
}

GLApi::~GLApi()
{
    if (GetCurrentGLApi() == this)
        SetGLApi(NULL);
}

GLApiBase::GLApiBase()
    : driver_(NULL)
{
}

GLApiBase::~GLApiBase()
{
}

void GLApiBase::InitializeBase(DriverGL* driver)
{
    driver_ = driver;
}

RealGLApi::RealGLApi()
{
#if DCHECK_IS_ON()
    filtered_exts_initialized_ = false;
#endif
}

RealGLApi::~RealGLApi()
{
}

void RealGLApi::Initialize(DriverGL* driver)
{
    InitializeWithCommandLine(driver, base::CommandLine::ForCurrentProcess());
}

void RealGLApi::InitializeWithCommandLine(DriverGL* driver,
    base::CommandLine* command_line)
{
    DCHECK(command_line);
    InitializeBase(driver);

    const std::string disabled_extensions = command_line->GetSwitchValueASCII(
        switches::kDisableGLExtensions);
    if (!disabled_extensions.empty()) {
        disabled_exts_ = base::SplitString(
            disabled_extensions, ", ;",
            base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
    }
}

void RealGLApi::glGetIntegervFn(GLenum pname, GLint* params)
{
    if (pname == GL_NUM_EXTENSIONS && disabled_exts_.size()) {
#if DCHECK_IS_ON()
        DCHECK(filtered_exts_initialized_);
#endif
        *params = static_cast<GLint>(filtered_exts_.size());
    } else {
        GLApiBase::glGetIntegervFn(pname, params);
    }
}

const GLubyte* RealGLApi::glGetStringFn(GLenum name)
{
    if (name == GL_EXTENSIONS && disabled_exts_.size()) {
#if DCHECK_IS_ON()
        DCHECK(filtered_exts_initialized_);
#endif
        return reinterpret_cast<const GLubyte*>(filtered_exts_str_.c_str());
    }
    return GLApiBase::glGetStringFn(name);
}

const GLubyte* RealGLApi::glGetStringiFn(GLenum name, GLuint index)
{
    if (name == GL_EXTENSIONS && disabled_exts_.size()) {
#if DCHECK_IS_ON()
        DCHECK(filtered_exts_initialized_);
#endif
        if (index >= filtered_exts_.size()) {
            return NULL;
        }
        return reinterpret_cast<const GLubyte*>(filtered_exts_[index].c_str());
    }
    return GLApiBase::glGetStringiFn(name, index);
}

void RealGLApi::glFlushFn()
{
    GLApiBase::glFlushFn();
}

void RealGLApi::glFinishFn()
{
    GLApiBase::glFinishFn();
}

void RealGLApi::InitializeFilteredExtensions()
{
    if (disabled_exts_.size()) {
        filtered_exts_.clear();
        if (gfx::WillUseGLGetStringForExtensions()) {
            filtered_exts_str_ = FilterGLExtensionList(reinterpret_cast<const char*>(
                                                           GLApiBase::glGetStringFn(GL_EXTENSIONS)),
                disabled_exts_);
            filtered_exts_ = base::SplitString(
                filtered_exts_str_, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
        } else {
            GLint num_extensions = 0;
            GLApiBase::glGetIntegervFn(GL_NUM_EXTENSIONS, &num_extensions);
            for (GLint i = 0; i < num_extensions; ++i) {
                const char* gl_extension = reinterpret_cast<const char*>(
                    GLApiBase::glGetStringiFn(GL_EXTENSIONS, i));
                DCHECK(gl_extension != NULL);
                if (!ContainsValue(disabled_exts_, gl_extension))
                    filtered_exts_.push_back(gl_extension);
            }
            filtered_exts_str_ = base::JoinString(filtered_exts_, " ");
        }
#if DCHECK_IS_ON()
        filtered_exts_initialized_ = true;
#endif
    }
}

TraceGLApi::~TraceGLApi()
{
}

NoContextGLApi::NoContextGLApi()
{
}

NoContextGLApi::~NoContextGLApi()
{
}

VirtualGLApi::VirtualGLApi()
    : real_context_(NULL)
    , current_context_(NULL)
{
}

VirtualGLApi::~VirtualGLApi()
{
}

void VirtualGLApi::Initialize(DriverGL* driver, GLContext* real_context)
{
    InitializeBase(driver);
    real_context_ = real_context;

    DCHECK(real_context->IsCurrent(NULL));
    extensions_ = real_context->GetExtensions();
    extensions_vec_ = base::SplitString(extensions_, " ", base::TRIM_WHITESPACE,
        base::SPLIT_WANT_ALL);
}

bool VirtualGLApi::MakeCurrent(GLContext* virtual_context, GLSurface* surface)
{
    bool switched_contexts = g_current_gl_context_tls->Get() != this;
    GLSurface* current_surface = GLSurface::GetCurrent();
    if (switched_contexts || surface != current_surface) {
        // MakeCurrent 'lite' path that avoids potentially expensive MakeCurrent()
        // calls if the GLSurface uses the same underlying surface or renders to
        // an FBO.
        if (switched_contexts || !current_surface || !virtual_context->IsCurrent(surface)) {
            if (!real_context_->MakeCurrent(surface)) {
                return false;
            }
        }
    }

    bool state_dirtied_externally = real_context_->GetStateWasDirtiedExternally();
    real_context_->SetStateWasDirtiedExternally(false);

    DCHECK_EQ(real_context_, GLContext::GetRealCurrent());
    DCHECK(real_context_->IsCurrent(NULL));
    DCHECK(virtual_context->IsCurrent(surface));

    if (state_dirtied_externally || switched_contexts || virtual_context != current_context_) {
#if DCHECK_IS_ON()
        GLenum error = glGetErrorFn();
        // Accepting a context loss error here enables using debug mode to work on
        // context loss handling in virtual context mode.
        // There should be no other errors from the previous context leaking into
        // the new context.
        DCHECK(error == GL_NO_ERROR || error == 0x0507 /*GL_CONTEXT_LOST_KHR*/) << "GL error was: " << error;
#endif

        // Set all state that is different from the real state
        GLApi* temp = GetCurrentGLApi();
        SetGLToRealGLApi();
        if (virtual_context->GetGLStateRestorer()->IsInitialized()) {
            GLStateRestorer* virtual_state = virtual_context->GetGLStateRestorer();
            GLStateRestorer* current_state = current_context_ ? current_context_->GetGLStateRestorer() : nullptr;
            if (switched_contexts || virtual_context != current_context_) {
                if (current_state)
                    current_state->PauseQueries();
                virtual_state->ResumeQueries();
            }

            virtual_state->RestoreState(
                (current_state && !state_dirtied_externally && !switched_contexts)
                    ? current_state
                    : NULL);
        }
        SetGLApi(temp);
        current_context_ = virtual_context;
    }
    SetGLApi(this);

    virtual_context->SetCurrent(surface);
    if (!surface->OnMakeCurrent(virtual_context)) {
        LOG(ERROR) << "Could not make GLSurface current.";
        return false;
    }
    return true;
}

void VirtualGLApi::OnReleaseVirtuallyCurrent(GLContext* virtual_context)
{
    if (current_context_ == virtual_context)
        current_context_ = NULL;
}

void VirtualGLApi::glGetIntegervFn(GLenum pname, GLint* params)
{
    switch (pname) {
    case GL_NUM_EXTENSIONS:
        *params = static_cast<GLint>(extensions_vec_.size());
        break;
    default:
        driver_->fn.glGetIntegervFn(pname, params);
        break;
    }
}

const GLubyte* VirtualGLApi::glGetStringFn(GLenum name)
{
    switch (name) {
    case GL_EXTENSIONS:
        return reinterpret_cast<const GLubyte*>(extensions_.c_str());
    default:
        return driver_->fn.glGetStringFn(name);
    }
}

const GLubyte* VirtualGLApi::glGetStringiFn(GLenum name, GLuint index)
{
    switch (name) {
    case GL_EXTENSIONS:
        if (index >= extensions_vec_.size())
            return NULL;
        return reinterpret_cast<const GLubyte*>(extensions_vec_[index].c_str());
    default:
        return driver_->fn.glGetStringiFn(name, index);
    }
}

void VirtualGLApi::glFlushFn()
{
    GLApiBase::glFlushFn();
}

void VirtualGLApi::glFinishFn()
{
    GLApiBase::glFinishFn();
}

} // namespace gfx
